package speed2010.andreza.vspeed.view;

import java.util.ArrayList;
import java.util.Hashtable;

import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;

import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntResource;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

/**
 * This class converts the given OWL Model to a Prefuse graph datastructure.
 * <p/>
 * Project OWL2Prefuse <br/>
 * OWLGraphConverter.java created 3 januari 2007, 9:58
 * <p/>
 * Copyright &copy 2006 Jethro Borsje
 * 
 * @author <a href="mailto:info@jborsje.nl">Jethro Borsje</a>
 * @version $$Revision:$$, $$Date:$$
 */
public class OWLGraphConverter extends Converter {
	/**
	 * The created Prefuse graph.
	 */
	private Graph m_graph;

	/**
	 * An ArrayList containing all the edges that have to be added to the Prefuse graph.
	 */
	private ArrayList<String[]> m_edges;

	/**
	 * An Hashtable containing all the nodes in Prefuse graph.
	 */
	private Hashtable<String, Node> m_nodes;

	/**
	 * An ArrayList containing the URI's of OWL classes that should not be converted into the Prefuse graph.
	 */
	private ArrayList<String> m_uselessType;

	/**
	 * Creates a new instance of OWLGraphConverter
	 * 
	 * @param p_OWLFile
	 *            The path to the OWL file that needs to be converted.
	 * @param p_directed
	 *            A boolean indicating whether the Prefuse graph needs to be directed.
	 */
	public OWLGraphConverter(final String p_OWLFile, final boolean p_directed) {
		super(p_OWLFile);
		init(p_directed);
	}

	/**
	 * Creates a new instance of OWLGraphConverter
	 * 
	 * @param p_model
	 *            The Jena model that needs to be converted.
	 * @param p_directed
	 *            A boolean indicating whether the Prefuse graph needs to be directed.
	 */
	public OWLGraphConverter(final OntModel p_model, final boolean p_directed) {
		super(p_model);
		init(p_directed);
	}

	/**
	 * Initialize the graph converter.
	 * 
	 * @param p_directed
	 *            A boolean indicating whether the Prefuse graph needs to be directed.
	 */
	private void init(final boolean p_directed) {
		this.m_edges = new ArrayList<String[]>();
		this.m_nodes = new Hashtable<String, Node>();

		// Create an ArrayList which contains URI's of nodes we do not want to
		// visualize, because they are to general.
		this.m_uselessType = new ArrayList<String>();
		this.m_uselessType.add("http://www.w3.org/2002/07/owl#Class");
		this.m_uselessType.add("http://www.w3.org/2000/01/rdf-schema#Class");

		// Create the graph.
		createGraph(p_directed);
	}

	/**
	 * Return the created Prefuse graph.
	 * 
	 * @return The created Prefuse graph.
	 */
	public Graph getGraph() {
		return this.m_graph;
	}

	/**
	 * Create the Prefuse graph. This method creates an empty graph and adds the appropriate columns to the node- and
	 * edgestable. After that it gets the root class (owl:Thing) of the OWL graph and recursively starts building the
	 * graph from there. This method is automatically called from the constructors of this converter.
	 * 
	 * @param p_directed
	 *            A boolean indicating whether the Prefuse graph needs to be directed.
	 */
	private void createGraph(final boolean p_directed) {
		// Create a new empty graph.
		this.m_graph = new Graph(p_directed);

		// Add the appropriate columns.
		this.m_graph.getNodeTable().addColumn("URI", String.class);
		this.m_graph.getNodeTable().addColumn("name", String.class);
		this.m_graph.getNodeTable().addColumn("type", String.class);
		this.m_graph.getEdgeTable().addColumn("label", String.class);

		// Get the root node.
		final OntClass rootClass = this.m_model.getOntClass("http://www.w3.org/2002/07/owl#Thing");

		// Build the entire tree.
		buildGraph(rootClass);

		// All the edges are stored in an ArrayList, because they can only be added
		// if all the appropriate nodes exist. At this point this is the case, so
		// all the nodes are created.
		createEdges();
	}

	/**
	 * Build the Prefuse graph, this method is called recursively.
	 * 
	 * @param p_currentClass
	 *            The class which is being added to the graph.
	 */
	private void buildGraph(final OntClass p_currentClass) {
		// If there is no root node yet, one is created.
		final Node currNode = this.m_graph.addNode();
		currNode.setString("URI", p_currentClass.getURI());
		currNode.setString("name", p_currentClass.getLocalName());
		currNode.setString("type", "class");

		// Walk trough the subclasses of the current class.
		final ExtendedIterator itClasses = p_currentClass.listSubClasses(true);
		while (itClasses.hasNext()) {
			// Recurse trough the subclasses of the current node.
			buildGraph((OntClass) itClasses.next());
		}

		// Add this node to the nodes ArrayList.
		this.m_nodes.put(p_currentClass.getURI(), currNode);

		// Add the edges, connected to this node, to the edges ArrayList.
		storeEdges(p_currentClass);
	}

	/**
	 * Temporarily store the edges which need to be added the graph. All the edges are stored in an ArrayList, because
	 * they can only be added if all the appropriate nodes exist. At this point this is the case, so all the nodes are
	 * created.
	 * 
	 * @param p_resource
	 *            The Jena OntResource of which the edges need to be stored.
	 */
	private void storeEdges(final OntResource p_resource) {
		final String sourceURI = p_resource.getURI();

		if (!this.m_uselessType.contains(sourceURI)) {
			final StmtIterator itProperties = p_resource.listProperties();
			while (itProperties.hasNext()) {
				final Statement property = itProperties.nextStatement();
				final String localName = property.getPredicate().getLocalName();

				if (property.getObject().isResource()) {
					final String targetURI = ((Resource) property.getObject()).getURI();
					if (!this.m_uselessType.contains(targetURI) && (targetURI != null)) {
						final String[] edge = new String[3];
						edge[0] = sourceURI;
						edge[1] = localName;
						edge[2] = targetURI;
						this.m_edges.add(edge);
					}
				}
			}
		}
	}

	/**
	 * Create edges for the relevant properties of the resource.
	 */
	private void createEdges() {
		for (int i = 0; i < this.m_edges.size(); i++) {
			final String[] strEdge = this.m_edges.get(i);

			// Get the source and the target node.
			final Node source = this.m_nodes.get(strEdge[0]);
			final Node target = this.m_nodes.get(strEdge[2]);

			final Edge edge = this.m_graph.addEdge(source, target);
			edge.setString("label", strEdge[1]);
		}
	}
}